#include "chatservice.hpp"
#include "public.hpp"

#include <muduo/base/Logging.h> // muduo日志系统
#include <vector>
#include <map>

#include <iostream>
using namespace std;
using namespace muduo;

ChatService *ChatService::instance()
{
    static ChatService service;
    return &service;
}

// 注册消息以及对应的Handler回调操作
ChatService::ChatService()
{
    _msgHandlerMap.insert({LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3)});
    _msgHandlerMap.insert({REG_MSG, std::bind(&ChatService::reg, this, _1, _2, _3)});

    // 绑定 一对一服务处理功能
    _msgHandlerMap.insert({ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, _1, _2, _3)});

    // 绑定添加好友功能
    _msgHandlerMap.insert({ADD_FRIEND_MSG, std::bind(&ChatService::addFriend, this, _1, _2, _3)});

    // 绑定群组功能
    _msgHandlerMap.insert({CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({ADD_GROUP_MSG, std::bind(&ChatService::addGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, _1, _2, _3)});

    // 注销
    _msgHandlerMap.insert({LOGINOUT_MSG, std::bind(&ChatService::logout, this, _1, _2, _3)});

    // 链接redis服务器
    if (_redis.connect())
    {
        _redis.init_notify_handler(std::bind(&ChatService::handleRedisSubscribeMessage, this, _1, _2));
    }
}

// 服务器异常，业务重置方法
void ChatService::reset()
{
    // 重置用户状态 为 offline
    _userModel.resetState();
}
// 获取消息对应的处理器
MsgHandler ChatService::getHandler(int msgid)
{
    // 记录错误日志 ， msgid 没有对应的事件处理回调函数
    auto it = _msgHandlerMap.find(msgid);
    if (it == _msgHandlerMap.end())
    {
        // 返回一个默认的处理器，什么都不做
        return [=](const TcpConnectionPtr &conn, json &js, Timestamp time)
        {
            LOG_ERROR << "msgid: " << msgid << " can not find handler!";
        };
    }
    else
    {
        return _msgHandlerMap[msgid];
    }
}

// 处理 登录的业务
//  和 数据库也要解耦合
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int id = js["id"].get<int>(); // js默认输出的是字符串，要转成整形
    string pwd = js["password"];

    User user = _userModel.query(id);
    if (user.getId() != -1 && user.getPwd() == pwd) // 找到了，且密码正确
    {
        if (user.getState() == "online")
        { // 该用户已经登入过了，不允许重复登录
            json response;
            response["msgid"] = LOGIN_MSG_ACK; // 消息状态是 登陆消息的返回
            response["errno"] = 2;             // 相应错误码， 2表示 重复登录
            response["errmsg"] = "该账号已经登录,请重新输入新账号";

            conn->send(response.dump()); // 发送回去
        }
        else
        {
            // 登录成功，记录用户链接信息
            { // 加锁
                lock_guard<mutex> lock(_connMutex);
                _userConnMap[id] = conn; // 多线程同时向map表加信息，要考虑线程安全问题
            }                            // 解锁 ，减少锁的粒度

            // 登录成功后，向redis 订阅 channel(id)
            _redis.subscribe(id);

            // 登陆成功 ，更新用户状态 为 online
            user.setState("online");
            _userModel.updateState(user);

            json response;
            response["msgid"] = LOGIN_MSG_ACK; // 消息状态是 登陆消息的返回
            response["errno"] = 0;             // 相应错误码， 0表示没错
            response["id"] = user.getId();
            response["name"] = user.getName();

            // 查询该用户是否有离线消息
            vector<string> vec = _offlineMsgModel.query(id);
            // 如果有离线消息
            if (!vec.empty())
            {
                response["offlinemsg"] = vec; // 带回给用户

                // 读取该用户的离线消息后，把该用户的离线消息删除掉
                _offlineMsgModel.remove(id);
            }

            // 查询该用户的好友信息并返回
            vector<User> userVec = _friendModel.query(id);
            if (!userVec.empty())
            {
                // response["friends"] = userVec; 该方法不行，User是自定义类型
                // 解决： 利用 string 类型 存储 一个user 对应的 json
                vector<string> vec2;
                for (User &user : userVec)
                {
                    json js;
                    js["id"] = user.getId();
                    js["name"] = user.getName();
                    js["state"] = user.getState();

                    vec2.push_back(js.dump());
                }
                response["friends"] = vec2;
            }

            // 群组消息也返回
            vector<Group> groupuserVec = _groupModel.queryGroups(id);
            if (!groupuserVec.empty())
            {
                vector<string> groupV;
                for (Group &group : groupuserVec)
                {
                    // 组的信息
                    json grpjson;
                    grpjson["id"] = group.getId();
                    grpjson["groupname"] = group.getName();
                    grpjson["groupdesc"] = group.getDesc();

                    // 组成员的信息
                    vector<string> userV;
                    for (GroupUser &user : group.getUsers())
                    {
                        json js;
                        js["id"] = user.getName();
                        js["name"] = user.getName();
                        js["state"] = user.getState();
                        js["role"] = user.getRole();
                        userV.push_back(js.dump());
                    }
                    grpjson["users"] = userV;
                    groupV.push_back(grpjson.dump()); // 将组的 信息加入群中
                }
                response["groups"] = groupV;
            }

            conn->send(response.dump()); // 发送回去
        }
    }
    else // 登陆失败
    {
        json response;
        response["msgid"] = LOGIN_MSG_ACK; // 消息状态是 登陆消息的返回
        response["errno"] = 1;             // 相应错误码， 1表示 登陆失败和注册失败
        response["errmsg"] = "用户名或者密码错误";
        conn->send(response.dump()); // 发送回去
    }
}

// 处理注册的业务    name password
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    string name = js["name"];
    string pwd = js["password"];

    User user;
    user.setName(name);
    user.setPwd(pwd);
    bool state = _userModel.insert(user);

    if (state) // 注册成功
    {
        json response;
        response["msgid"] = REG_MSG_ACK;
        response["errno"] = 0; // 相应错误码， 0表示没错
        response["id"] = user.getId();

        conn->send(response.dump()); // 发送回去
    }
    else // 注册失败
    {
        json response;
        response["msgid"] = REG_MSG_ACK;
        response["errno"] = 1; // 相应错误码， 1 表示 登陆失败和注册失败

        conn->send(response.dump()); // 发送回去
    }
}

// 处理注销业务
void ChatService::logout(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();

    { // 从在线表中删除
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(userid);
        if (it != _userConnMap.end())
        {
            _userConnMap.erase(it);
        }
    }

    // 用户注销，取消订阅
    _redis.unsubscribe(userid);

    // 更新消息
    User user(userid, "", "", "offline");
    _userModel.updateState(user);
}
// 客户端异常关闭了
void ChatService::clientCloseException(const TcpConnectionPtr &conn)
{
    User user;

    { // 遍历唯一哈希表要加锁
        lock_guard<mutex> lock(_connMutex);
        // 遍历哈希表找到该链接对应的 在线id
        for (auto it = _userConnMap.begin(); it != _userConnMap.end(); ++it)
        {
            if (it->second == conn)
            {
                user.setId(it->first);  // 设置好要修改的 id,方便 下面修改
                _userConnMap.erase(it); // 从 map 表 删除用户的链接信息
                break;
            }
        }
    }

    // 用户注销，取消订阅
    _redis.unsubscribe(user.getId());

    // 更新用户的状态信息
    if (user.getId() != -1)
    {
        user.setState("offline");
        _userModel.updateState(user);
    }
}

// 一对一聊天业务
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int toid = js["toid"].get<int>();

    {
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(toid);
        if (it != _userConnMap.end())
        {
            // toid在线，转发消息
            // 通过TcpConnectionPtr ，服务器转发出去 消息给toid用户了
            it->second->send(js.dump());
            return;
        }
    }

    // 在当前主机上没找着
    // 查询数据库中，用户是否在线
    // 在线的话，那么就在另外一台主机上 , 所以向 redis服务器  发布
    User user = _userModel.query(toid);
    if (user.getState() == "online")
    {
        _redis.publish(toid, js.dump());
        return;
    }

    _offlineMsgModel.insert(toid, js.dump());
}

// 添加好友业务 msgid id friendid
void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();
    int friendid = js["friendid"].get<int>();

    // 存储好友信息
    _friendModel.insert(userid, friendid);
}

// 创建群组业务
void ChatService::createGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();
    string name = js["groupname"];
    string desc = js["groupdesc"];

    Group group(-1, name, desc);
    if (_groupModel.createGroup(group))
    {
        // 存储群组的创建人信息
        _groupModel.addGroup(userid, group.getId(), "creator");
    }
}

// 加入群组业务
void ChatService::addGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();
    int groupid = js["groupid"].get<int>();
    _groupModel.addGroup(userid, groupid, "normal");
}

// 群组聊天业务
void ChatService::groupChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();
    int groupid = js["groupid"].get<int>();

    vector<int> useridVec = _groupModel.queryGroupUsers(userid, groupid);

    // 加锁，因为要访问 在线用户表
    lock_guard<mutex> lock(_connMutex);
    for (int id : useridVec)
    {
        auto it = _userConnMap.find(id);
        if (it != _userConnMap.end())
        { // 在线就发过去了
            it->second->send(js.dump());
        }
        else
        {
            // 在当前主机上没找着
            // 查询数据库中，用户是否在线
            // 在线的话，那么就在另外一台主机上 , 所以向 redis服务器  发布
            User user = _userModel.query(id);
            if (user.getState() == "online")
            {
                _redis.publish(id, js.dump());
            }
            else
            {
                // 离线则存储起来
                _offlineMsgModel.insert(id, js.dump());
            }
        }
    }
}

//从redis消息队列中 获取订阅的消息
void ChatService::handleRedisSubscribeMessage(int userid,string msg)
{
    //查找 userid
    {
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(userid);
        if(it != _userConnMap.end())
        {
            it->second->send(msg);
            return;
        }
    }

    //假如正好下线了，存储离线消息
    _offlineMsgModel.insert(userid,msg);
}
